package com.nurun.cacheimageloader;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.nurun.cacheimageloader.util.ImageNameGenerator;

public abstract class AbstractDiskCacheLimited implements DiskCacheInterface {

	private int cacheSize = 0;

	private long sizeLimit;
	
	private File cacheDir;

    private ImageNameGenerator imageNameGenerator;

	private final Map<File, Long> lastUsageDates = Collections.synchronizedMap(new HashMap<File, Long>());

	/**
	 * @param cacheDir
	 *            Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's needed
	 *            for right cache limit work.
	 * @param sizeLimit
	 *            Cache limit value. If cache exceeds this limit then file with the most oldest last usage date will be
	 *            deleted.
	 */
	public AbstractDiskCacheLimited(File cacheDir, long sizeLimit) {
		this(cacheDir, ImageNameGenerator.createDefault(), sizeLimit);
	}

	/**
	 * @param cacheDir
	 *            Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's needed
	 *            for right cache limit work.
	 * @param imageNameGenerator
	 *            Name generator for cached files
	 * @param sizeLimit
	 *            Cache limit value. If cache exceeds this limit then file with the most oldest last usage date will be
	 *            deleted.
	 */
	public AbstractDiskCacheLimited(File cacheDir, ImageNameGenerator imageNameGenerator, long sizeLimit) {
	    this.cacheDir = cacheDir;
        this.imageNameGenerator = imageNameGenerator;        
		this.sizeLimit = sizeLimit;
		calculateCacheSizeAndFillUsageMap();
	}

	private void calculateCacheSizeAndFillUsageMap() {
		int size = 0;
		File[] cachedFiles = getCacheDir().listFiles();
		for (File cachedFile : cachedFiles) {
			size += getSize(cachedFile);
			lastUsageDates.put(cachedFile, cachedFile.lastModified());
		}
		cacheSize = size;
	}

	@Override
	public void put(String key, File file) {
		int valueSize = getSize(file);
		while (cacheSize + valueSize > sizeLimit) {
			int freedSize = removeNext();
			if (freedSize == 0) break; // cache is empty (have nothing to delete)
			cacheSize -= freedSize;
		}
		cacheSize += valueSize;

		Long currentTime = System.currentTimeMillis();
		file.setLastModified(currentTime);
		lastUsageDates.put(file, currentTime);
	}

	@Override
	public File get(String key) {
	    String fileName = imageNameGenerator.generate(key);
	    File file = new File(cacheDir, fileName);

		Long currentTime = System.currentTimeMillis();
		file.setLastModified(currentTime);
		lastUsageDates.put(file, currentTime);

		return file;
	}

	@Override
	public void clear() {
		lastUsageDates.clear();
		cacheSize = 0;
		
		File[] files = cacheDir.listFiles();
        if (files != null) {
            for (File f : files) {
                f.delete();
            }
        }
//		super.clear();
	}

	/** Remove next file and returns it's size */
	private int removeNext() {
		if (lastUsageDates.isEmpty()) {
			return 0;
		}

		Long oldestUsage = null;
		File mostLongUsedFile = null;
		Set<Entry<File, Long>> entries = lastUsageDates.entrySet();
		synchronized (lastUsageDates) {
			for (Entry<File, Long> entry : entries) {
				if (mostLongUsedFile == null) {
					mostLongUsedFile = entry.getKey();
					oldestUsage = entry.getValue();
				} else {
					Long lastValueUsage = entry.getValue();
					if (lastValueUsage < oldestUsage) {
						oldestUsage = lastValueUsage;
						mostLongUsedFile = entry.getKey();
					}
				}
			}
		}

		int fileSize = getSize(mostLongUsedFile);
		if (mostLongUsedFile.delete()) {
			lastUsageDates.remove(mostLongUsedFile);
		}
		return fileSize;
	}

	protected File getCacheDir() {
        return cacheDir;
    }
	
	protected abstract int getSize(File file);
}
